#include "StdAfx.h"
#include "CoordinationActorAssignment.h"
#include "CoordinationScriptEnvironment.h"


CoordinationActorAssignment::CoordinationActorAssignment(
	const CoordinationActors& actors, const CoordinationRoles& roles)
	: m_actors(actors)
	, m_roles(roles)
{
}

void CoordinationActorAssignment::ReserveRoles(int roleCount)
{
	m_workingRoles.reserve(roleCount);
}

void CoordinationActorAssignment::AddRole(int roleId)
{
	m_workingRoles.push_back(SWorkingRole(roleId));
}

void CoordinationActorAssignment::Process(const CoordinationScriptEnvironment& environment)
{
	FUNCTION_PROFILER( GetISystem(), PROFILE_AI );

	CTimeValue start = gEnv->pTimer->GetAsyncTime();

	ScoreCandidates(environment);
	SortRolesByBestCandidate();

	uint32 assignedCount = 0;
	uint32 availableCount = m_actors.size();
	uint32 roleCount = m_workingRoles.size();
	uint32 roleIdx = 0;

	// First iteration only fulfil minimum requirements
	while ((assignedCount < availableCount) && (roleIdx < roleCount))
	{
		SWorkingRole& workingRole = m_workingRoles[roleIdx++];
		const CoordinationRole& role = m_roles[workingRole.roleId];

		if (!workingRole.candidates.empty() 
			&& ((int)workingRole.assignees.size() < role.GetMinActors()))
		{
			roleIdx = 0;
			++assignedCount;

			CoordinationActor candidate = workingRole.candidates.front().actor;
			workingRole.assignees.push_back(candidate);

			InvalidateCandidate(candidate);

			SortRolesByBestCandidate();
		}
	}

	roleIdx = 0;
	SortRolesByNeed();

	// Second iteration will distribute remaining actors by need
	uint32 loopAssigned = 0;
	while (assignedCount < availableCount)
	{
		SWorkingRole& workingRole = m_workingRoles[roleIdx++];
		const CoordinationRole& role = m_roles[workingRole.roleId];

		if (!workingRole.candidates.empty() 
			&& ((int)workingRole.assignees.size() < role.GetMaxActors()))
		{
			++assignedCount;
			++loopAssigned;

			CoordinationActor candidate = workingRole.candidates.front().actor;
			workingRole.assignees.push_back(candidate);

			InvalidateCandidate(candidate);
		}

		if (roleIdx >= roleCount)
		{
			if (!loopAssigned)
				break;

			loopAssigned = 0;
			roleIdx = 0;
		}
	}

	CTimeValue duration = gEnv->pTimer->GetAsyncTime() - start;

	static bool showBreakdown = false;
	if (showBreakdown)
	{
		CryLogAlways("Role Assignment Breakdown");
		CryLogAlways("---------------------------------");

		roleCount = m_workingRoles.size();
		if (roleCount > 0)
		{
			uint32 actorCount = m_actors.size();
			assignedCount = 0;

			for (uint32 i = 0; i < roleCount; ++i)
			{
				SWorkingRole& workingRole = m_workingRoles[i];
				const CoordinationRole& role = m_roles[workingRole.roleId];

				uint32 assigneeCount = workingRole.assignees.size();
				assignedCount += assigneeCount;

				string assignees;

				for (uint j = 0; j < workingRole.assignees.size(); ++j)
				{
					assignees += workingRole.assignees[j].GetName();
					if (j < workingRole.assignees.size()-1)
						assignees += ", ";
				}

				CryLogAlways("%s (%d/%d) Assigned: %d - %s", role.GetName(), role.GetMinActors(), 
					role.GetMaxActors() > 88 ? 88 : role.GetMaxActors(), (int32)workingRole.assignees.size(), assignees.c_str());
			}
		}

		CryLogAlways("Available: %d // Assigned: %d // Unassigned: %d", (uint32)m_actors.size(), (uint32)assignedCount,
			(uint32)(m_actors.size()-assignedCount));

		CryLogAlways("Assigning %d actors to %d roles took %.3fms...", (uint32)m_actors.size(), roleCount, duration.GetMilliSeconds());
	}
}

const CoordinationActors& CoordinationActorAssignment::GetAssignees(int roleId) const
{
	uint32 roleCount = m_workingRoles.size();

	for (uint32 i = 0; i < roleCount; ++i)
	{
		const SWorkingRole& workingRole = m_workingRoles[i];
		if (workingRole.roleId == roleId)
			return workingRole.assignees;
	}

	assert(0);

	static CoordinationActors empty;

	return empty;
}

void CoordinationActorAssignment::ScoreCandidates(const CoordinationScriptEnvironment& environment)
{
	FUNCTION_PROFILER( GetISystem(), PROFILE_AI );

	uint32 roleCount = m_workingRoles.size();
	uint32 actorCount = m_actors.size();

	for (uint32 a = 0; a < actorCount; ++a)
	{
		const CoordinationActor& actor = m_actors[a];

		environment.SetActor(actor);
		environment.Apply();

		for (uint32 i = 0; i < roleCount; ++i)
		{
			SWorkingRole& workingRole = m_workingRoles[i];
			const CoordinationRole& role = m_roles[workingRole.roleId];

			float score = role.GetActorScore(actor);
			//Filter out negative scores here. This way we can exclude certain actors based on score. -Morgan 12/06/2010
			if(score >= 0)
			{
				workingRole.candidates.push_back(SCandidate(actor, score));
			}
		}
	}

	for (uint32 i = 0; i < roleCount; ++i)
	{
		SWorkingRole& workingRole = m_workingRoles[i];

		std::sort(workingRole.candidates.begin(), workingRole.candidates.end());
	}
}

void CoordinationActorAssignment::InvalidateCandidate(const CoordinationActor& actor)
{
	FUNCTION_PROFILER( GetISystem(), PROFILE_AI );

	uint32 roleCount = m_workingRoles.size();

	for (uint32 i = 0; i < roleCount; ++i)
	{
		SWorkingRole& workingRole = m_workingRoles[i];

		Candidates::iterator it = workingRole.candidates.begin();
		Candidates::iterator end = workingRole.candidates.end();
		
		for ( ; it != end; ++it)
		{
			if (it->actor == actor)
			{
				workingRole.candidates.erase(it);
				break;
			}
		}
	}
}

void CoordinationActorAssignment::SortRolesByBestCandidate()
{
	FUNCTION_PROFILER( GetISystem(), PROFILE_AI );

	std::sort(m_workingRoles.begin(), m_workingRoles.end());
}

void CoordinationActorAssignment::SortRolesByNeed()
{
	FUNCTION_PROFILER( GetISystem(), PROFILE_AI );

	std::sort(m_workingRoles.begin(), m_workingRoles.end(), sort_by_need(m_roles));
}
